using System;
using System.Collections;
using System.IO;
using System.Xml;
using System.Xml.Xsl;
using NUnit.Framework;

namespace PaulRz.Util
{
	/// <summary>
	/// Summary description for XsltCache.
	/// </summary>
	public sealed class XsltCache
	{
		private FileSystemEventHandler stdHandler;
		
		private XsltCache()
		{
			stdHandler = new FileSystemEventHandler(onFileChanged);
		}

		private void onFileChanged(object sender, FileSystemEventArgs e)
		{
			string fName = e.FullPath;
			CacheElement ce = cache[fName] as CacheElement;
			if (ce != null)
			{
				cache.Remove(fName);
				ce.Dispose();
			}
		}

		#region Singleton impl
		
		private static XsltCache _self = null;
		
		public static XsltCache Instance
		{
			get
			{
				if (_self == null) _self = new XsltCache();
				return _self;
			}
		}
		
		#endregion
		
		private Hashtable cache = Hashtable.Synchronized(new HashtableCI());
		
		private static string getFullName(string fName)
		{
			return FUtil.getConfigPath(fName);
		}
		
		/// <summary>
		/// Retrieves the object from cache if exists, otherwise create new one.
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		public XslCompiledTransform GetXslTransform(string fileName)
		{
			string fullName = getFullName(fileName);
			
			CacheElement ce = cache[fullName] as CacheElement;
			if (ce!=null)
			{
				return ce.Transform;
			} else
			{
				ce = new CacheElement(fullName, stdHandler);
				cache.Add(fullName, ce);
				return ce.Transform;
			}
		}
		
		/// <summary>
		/// Check if the object is cached
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns>Null if it's not present</returns>
		public XslCompiledTransform IsCached(string fileName)
		{
			string fName = getFullName(fileName);
			CacheElement ce = cache[fName] as CacheElement;
			if (ce != null)
				return ce.Transform;
			else 
				return null;
		}
		
		/// <summary>
		/// Get cached object if exits, otherwise null
		/// </summary>
		public XslCompiledTransform this[string fileName]
		{
			get
			{
				return IsCached(fileName);
			}
		}
		
		public void Flush()
		{
			lock(cache.SyncRoot)
			{
				foreach (CacheElement value in cache.Values)
				{
					value.Dispose();
				}
			}
			cache.Clear();
		}

		#region CacheElement
		
		private class CacheElement : IDisposable
		{
			private FileSystemWatcher fsw;
			
			public CacheElement(string fName, FileSystemEventHandler fseh)
			{
				if (File.Exists(fName))
				{
					cachedTransfrom = new XslCompiledTransform();
				    XsltSettings xsets = new XsltSettings(true, true);
				    XmlResolver xresolve = new XmlUrlResolver();
					cachedTransfrom.Load(fName, xsets, xresolve);
					
					fsw = new FileSystemWatcher(Path.GetDirectoryName(fName), Path.GetFileName(fName));
					fsw.Changed += fseh;
					fsw.Deleted += fseh;
					fsw.EnableRaisingEvents = true;
				} 
				else throw new FileNotFoundException("File for caching is not found", fName);
			}

			~CacheElement()
			{
				Dispose(false);
			}
			
			private XslCompiledTransform cachedTransfrom = null;
			
			public XslCompiledTransform Transform
			{
				get
				{
					return cachedTransfrom;
				}
			}
			#region IDisposable Members
			
			private void Dispose(bool isDisposing)
			{
				if (fsw!=null)
				{
					fsw.EnableRaisingEvents = false;
					fsw.Dispose();
				}
				cachedTransfrom = null;
				if (isDisposing)
					GC.SuppressFinalize(this);
			}

			public void Dispose()
			{
				Dispose(true);
			}

			#endregion
		}
		#endregion
	}
	
}
